using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using CloudWars.Units;
using CloudWars.Animation;
using CloudWars.Tiles;

namespace CloudWars
{
    /// <summary>
    /// This is the main type for your game
    /// </summary>
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        public static int GRID_TILE_SIZE
        {
            get
            {
                return THE_GRID.TileSize;
            }
        }

        public static int GRID_HEIGHT
        {
            get
            {
                return THE_GRID.GridHeight;
            }
        }

        public static int GRID_WIDTH
        {
            get
            {
                return THE_GRID.GridWidth;
            }
        }

        public const int INPUT_LOCKOUT_MILLISECONDS = 100;

        public static Grid THE_GRID;

        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;

        Texture2D sidebarTexture;
        Texture2D buttonATexture;
        Texture2D buttonYTexture;
        SpriteFont directionFont;
        FloatingText floatingHelpMessages;

        SpriteAnimation cursorAnimation;
        SpriteFont nameFont;
        SpriteFont titleFont;

        Point cursorGridLocation;
        int inputLockoutCounter = 0;
        bool preventCursorInput = false;
        bool preventButtonInput = false;

        List<Player> Players;
        Player CurrentPlayer;
        Unit CurrentSelectedUnit;

        Unit dyingUnit;

        public List<Unit> AllUnits
        {
            get
            {
                List<Unit> list = new List<Unit>();

                foreach (Player player in Players)
                {
                    list.AddRange(player.Units);
                }

                return list;
            }
        }

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
            THE_GRID = new Grid(16, 10, 50);
        }

        /// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected override void Initialize()
        {
            base.Initialize();
            graphics.PreferredBackBufferHeight = 500;
            graphics.ApplyChanges();

            cursorGridLocation = new Point(4, 0);

            Players = new List<Player>();

            Player player1 = new Player("Player 1", PlayerIndex.One, Players, AnnouncePlayerDefeatEvent) { Color = Color.Red };
            player1.Units.Add(new Infantry(player1, "Bob", 4, 1, RemoveDeadUnit));
            player1.Units.Add(new Infantry(player1, "Joe", 5, 0, RemoveDeadUnit));
            player1.Units.Add(new Soldier(player1, "Jeff", 5, 1, RemoveDeadUnit));
            player1.Units.Add(new Pyro(player1, "Lu", 4, 0, RemoveDeadUnit));

            Player player2 = new Player("Player 2", PlayerIndex.Two, Players, AnnouncePlayerDefeatEvent) { Color = Color.Blue };
            player2.Units.Add(new Infantry(player2, "Bob", 14, 0, RemoveDeadUnit) { Facing = Direction.Left });
            player2.Units.Add(new Infantry(player2, "Joe", 15, 1, RemoveDeadUnit) { Facing = Direction.Left });
            player2.Units.Add(new Pyro(player2, "Lu", 15, 0, RemoveDeadUnit) { Facing = Direction.Left });
            player2.Units.Add(new Soldier(player2, "Jeff", 14, 1, RemoveDeadUnit) { Facing = Direction.Left });

            Player player3 = new Player("Player 3", PlayerIndex.Three, Players, AnnouncePlayerDefeatEvent) { Color = Color.Orange };
            player3.Units.Add(new Infantry(player3, "Bob", 5, 9, RemoveDeadUnit));
            player3.Units.Add(new Infantry(player3, "Joe", 4, 8, RemoveDeadUnit));
            player3.Units.Add(new Pyro(player3, "Lu", 4, 9, RemoveDeadUnit));
            player3.Units.Add(new Soldier(player3, "Jeff", 5, 8, RemoveDeadUnit));

            Player player4 = new Player("Player 4", PlayerIndex.Four, Players, AnnouncePlayerDefeatEvent) { Color = Color.Purple };
            player4.Units.Add(new Infantry(player4, "Bob", 14, 9, RemoveDeadUnit) { Facing = Direction.Left });
            player4.Units.Add(new Infantry(player4, "Joe", 15, 8, RemoveDeadUnit) { Facing = Direction.Left });
            player4.Units.Add(new Pyro(player4, "Lu", 15, 9, RemoveDeadUnit) { Facing = Direction.Left });
            player4.Units.Add(new Soldier(player4, "Jeff", 14, 8, RemoveDeadUnit) { Facing = Direction.Left });


            CurrentPlayer = player1;
        }

        public void AnnouncePlayerDefeatEvent(Player player)
        {
            floatingHelpMessages.ShowText(new Point(0, 110), string.Format("{0} DEFEATED", player.Name), player.Color);
        }

        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {
            spriteBatch = new SpriteBatch(GraphicsDevice);
            nameFont = Content.Load<SpriteFont>("NameFont");
            titleFont = Content.Load<SpriteFont>("TitleFont");

            sidebarTexture = Content.Load<Texture2D>("sidebar_background");
            buttonATexture = Content.Load<Texture2D>("button_A");
            buttonYTexture = Content.Load<Texture2D>("button_Y");
            directionFont = Content.Load<SpriteFont>("FloatingCombatText");
            floatingHelpMessages = new FloatingText(directionFont, 100);


            cursorAnimation = new SpriteAnimation(Content.Load<Texture2D>("animated_cursor"), 4);
            cursorAnimation.Position = new Vector2(GRID_TILE_SIZE, GRID_TILE_SIZE);
            cursorAnimation.IsLooping = true;
            cursorAnimation.FramesPerSecond = 5;

            Unit.LoadTextures(Content);
            Infantry.LoadTextures(Content);
            Soldier.LoadTextures(Content);
            Pyro.LoadTextures(Content);
            GrassTile.LoadTextures(Content);
            WaterTile.LoadTextures(Content);
            ProductionTile.LoadTextures(Content);
            PlainTile.LoadTextures(Content);
            MountainTile.LoadTextures(Content);
            WoodsTile.LoadTextures(Content);
            RiverTile.LoadTextures(Content);
            Grid.LoadTextures(Content);

            Texture2D whatever = new Texture2D(GraphicsDevice, 1, 1);
        }

        /// <summary>
        /// UnloadContent will be called once per game and is the place to unload
        /// all content.
        /// </summary>
        protected override void UnloadContent()
        {
            // TODO: Unload any non ContentManager content here
        }

        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime gameTime)
        {
            floatingHelpMessages.Update();
            cursorAnimation.Update(gameTime);

            // Allows the game to exit
            if (GamePad.GetState(PlayerIndex.One).IsButtonDown(Buttons.Back)
                || GamePad.GetState(CurrentPlayer.Controller).IsButtonDown(Buttons.Back)
                || Keyboard.GetState().IsKeyDown(Keys.Escape))
                this.Exit();

            if (preventCursorInput)
            {
                inputLockoutCounter += gameTime.ElapsedGameTime.Milliseconds;

                if (inputLockoutCounter >= INPUT_LOCKOUT_MILLISECONDS)
                {
                    inputLockoutCounter = 0;
                    preventCursorInput = false;
                }
            }
            else
            {
                GetCursorInput();
            }

            if (!preventButtonInput)
            {
                PerformButtonInput();
            }
            else if (noButtonsPressed())
            {
                preventButtonInput = false;
            }

            UpdateUnits(gameTime);

            //Unselect if no actions
            if (CurrentSelectedUnit != null && !CurrentSelectedUnit.HasActions)
            {
                DeselectCurrentUnit();
            }

            base.Update(gameTime);
        }

        private void PerformButtonInput()
        {
            if (GamePad.GetState(CurrentPlayer.Controller).IsButtonDown(Buttons.A)
                || GamePad.GetState(PlayerIndex.One).IsButtonDown(Buttons.A)
                || Keyboard.GetState().IsKeyDown(Keys.Space))
            {
                SelectTile();
                preventButtonInput = true;
            }

            if (GamePad.GetState(CurrentPlayer.Controller).IsButtonDown(Buttons.Y)
                || GamePad.GetState(PlayerIndex.One).IsButtonDown(Buttons.Y)
                || Keyboard.GetState().IsKeyDown(Keys.Tab))
            {
                EndCurrentPlayerTurn();
                preventButtonInput = true;
            }
            if (Keyboard.GetState().IsKeyDown(Keys.Delete))
            {
                var defeatedPlayer = CurrentPlayer;
                EndCurrentPlayerTurn();
                defeatedPlayer.Defeat();
                preventButtonInput = true;
            }
        }

        private bool noButtonsPressed()
        {
            return GamePad.GetState(CurrentPlayer.Controller).IsButtonUp(Buttons.A)
                && GamePad.GetState(CurrentPlayer.Controller).IsButtonUp(Buttons.Y)
                && GamePad.GetState(PlayerIndex.One).IsButtonUp(Buttons.A)
                && GamePad.GetState(PlayerIndex.One).IsButtonUp(Buttons.Y)
                && Keyboard.GetState().IsKeyUp(Keys.Tab)
                && Keyboard.GetState().IsKeyUp(Keys.Space)
                && Keyboard.GetState().IsKeyUp(Keys.Delete);
        }

        private void UpdateUnits(GameTime gameTime)
        {
            foreach (Player player in Players)
            {
                foreach (Unit unit in player.Units)
                {
                    unit.Update(gameTime);

                    if (unit.IsDying)
                    {
                        dyingUnit = unit;
                    }
                }
            }

            if (dyingUnit != null && dyingUnit.DeathAnimation.IsAnimationDone)
            {
                dyingUnit.Die();
                dyingUnit = null;
            }
        }

        public void RemoveDeadUnit(Unit unit)
        {
            unit.Owner.Units.Remove(unit);
        }

        private void GetCursorInput()
        {
            int stickX = (int)(GamePad.GetState(CurrentPlayer.Controller).ThumbSticks.Left.X * 2);
            int stickY = (int)(GamePad.GetState(CurrentPlayer.Controller).ThumbSticks.Left.Y * 2);

            if ((GamePad.GetState(CurrentPlayer.Controller).IsButtonDown(Buttons.DPadUp)
                || Keyboard.GetState().IsKeyDown(Keys.Up)
                || stickY > 0) && cursorGridLocation.Y > 0)
            {
                cursorGridLocation.Y--;
                preventCursorInput = true;
            }
            if ((GamePad.GetState(CurrentPlayer.Controller).IsButtonDown(Buttons.DPadDown)
                || Keyboard.GetState().IsKeyDown(Keys.Down)
                || stickY < 0) && cursorGridLocation.Y < GRID_HEIGHT - 1)
            {
                cursorGridLocation.Y++;
                preventCursorInput = true;
            }
            if ((GamePad.GetState(CurrentPlayer.Controller).IsButtonDown(Buttons.DPadRight)
                || Keyboard.GetState().IsKeyDown(Keys.Right)
                || stickX > 0) && cursorGridLocation.X < GRID_WIDTH - 1)
            {
                cursorGridLocation.X++;
                preventCursorInput = true;
            }
            if ((GamePad.GetState(CurrentPlayer.Controller).IsButtonDown(Buttons.DPadLeft)
                || Keyboard.GetState().IsKeyDown(Keys.Left)
                || stickX < 0) && cursorGridLocation.X > 4)
            {
                cursorGridLocation.X--;
                preventCursorInput = true;
            }
        }

        private void EndCurrentPlayerTurn()
        {
            DeselectCurrentUnit();

            foreach (Unit unit in CurrentPlayer.Units)
            {
                unit.WakeUnit();
            }

            var playerEnum = Players.GetEnumerator();

            while (playerEnum.Current != CurrentPlayer && playerEnum.MoveNext()) ;

            if (playerEnum.MoveNext())
            {
                CurrentPlayer = playerEnum.Current;
            }
            else
            {
                CurrentPlayer = Players.First();
            }

            cursorGridLocation = CurrentPlayer.Units.First().GridLocation;

            floatingHelpMessages.ShowText(new Point(0, 110), string.Format("{0}'s turn!", CurrentPlayer.Name), CurrentPlayer.Color);
        }

        private void DeselectCurrentUnit()
        {
            if (CurrentSelectedUnit != null)
            {
                CurrentSelectedUnit.IsSelected = false;
                CurrentSelectedUnit = null;
            }
        }

        private void SelectTile()
        {
            Unit unit = AllUnits.Where(u => cursorGridLocation == u.GridLocation).FirstOrDefault();

            if (unit != null)
            {
                if (CurrentPlayer.Units.Contains(unit))
                {
                    SelectUnit(unit);
                }
                else
                {
                    AttackUnit(unit);
                }
            }
            else
            {
                MoveUnit();
            }
        }

        private void SelectUnit(Unit unit)
        {
            if (unit.HasActions)
            {
                DeselectCurrentUnit();

                unit.IsSelected = true;
                CurrentSelectedUnit = unit;
            }
        }

        private void AttackUnit(Unit attackedUnit)
        {
            if (CurrentSelectedUnit != null
                && !CurrentSelectedUnit.HasAttacked
                && CurrentSelectedUnit.GridLocation.DistanceTo(attackedUnit.GridLocation) <= CurrentSelectedUnit.AttackRange)
            {
                CurrentSelectedUnit.AttackTarget = attackedUnit;
                int defense = THE_GRID.gridLayout[attackedUnit.GridLocation.X, attackedUnit.GridLocation.Y].Defense * 3;
                attackedUnit.TakeDamage(CurrentSelectedUnit.AttackPower - defense);
                CurrentSelectedUnit.HasAttacked = true;

                UpdateCurrentUnitFacing(attackedUnit.GridLocation);
            }
        }

        private List<Point> HighlightPossibleMovementSquares()
        {
            if (CurrentSelectedUnit == null || CurrentSelectedUnit.HasMoved || CurrentSelectedUnit.IsAttacking)
            {
                return new List<Point>();
            }

            var unitLocation = CurrentSelectedUnit.GridLocation;
            List<Point> validLocations = new List<Point>();

            for (int i = 0; i < GRID_HEIGHT; i++)
            {
                for (int j = 0; j < GRID_WIDTH; j++)
                {
                    Point pointToExamine = new Point(j, i);
                    if (pointToExamine.DistanceTo(CurrentSelectedUnit.GridLocation) <= CurrentSelectedUnit.MaxMove
                        && pointToExamine != CurrentSelectedUnit.GridLocation)
                    {
                        validLocations.Add(new Point(j, i));
                    }
                }
            }

            return validLocations;
        }

        private List<Point> HighlightPossibleAttackSquares()
        {
            if (CurrentSelectedUnit == null || CurrentSelectedUnit.HasAttacked || CurrentSelectedUnit.IsMoving)
            {
                return new List<Point>();
            }

            var unitLocation = CurrentSelectedUnit.GridLocation;
            List<Point> validLocations = new List<Point>();

            for (int i = 0; i < GRID_HEIGHT; i++)
            {
                for (int j = 0; j < GRID_WIDTH; j++)
                {
                    Point pointToExamine = new Point(j, i);
                    if (pointToExamine.DistanceTo(CurrentSelectedUnit.GridLocation) <= CurrentSelectedUnit.AttackRange
                        && pointToExamine != CurrentSelectedUnit.GridLocation)
                    {
                        validLocations.Add(new Point(j, i));
                    }
                }
            }

            return validLocations;
        }

        private void MoveUnit()
        {
            if (CurrentSelectedUnit == null)
            {
                // select a unit first
                return;
            }

            if (cursorGridLocation.DistanceTo(CurrentSelectedUnit.GridLocation) <= CurrentSelectedUnit.MaxMove
                && !CurrentSelectedUnit.HasMoved)
            {
                UpdateCurrentUnitFacing(cursorGridLocation);

                CurrentSelectedUnit.GridLocation = cursorGridLocation;
                CurrentSelectedUnit.HasMoved = true;
            }
            else
            {
                //can't move!
            }
        }

        private void UpdateCurrentUnitFacing(Point toFace)
        {
            if (toFace.X - CurrentSelectedUnit.GridLocation.X > 0)
            {
                CurrentSelectedUnit.Facing = Direction.Right;
            }
            else if (toFace.X - CurrentSelectedUnit.GridLocation.X < 0)
            {
                CurrentSelectedUnit.Facing = Direction.Left;
            }
        }

        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.Beige);

            spriteBatch.Begin();

            List<Point> tilesToMovementTint = HighlightPossibleMovementSquares();
            List<Point> tilesToAttackTint = HighlightPossibleAttackSquares();

            DrawGrid(tilesToMovementTint, tilesToAttackTint);
            DrawUnits();
            DrawCursor();

            DrawSidebar();

            if (Players.Count == 1)
            {
                DrawWinScreen();
            }

            spriteBatch.End();

            base.Draw(gameTime);
        }

        Vector2 winPos = new Vector2();
        int winDX = 5;
        int winDY = 5;

        private void DrawWinScreen()
        {
            winPos = new Vector2(winPos.X + winDX, winPos.Y + winDY);
            if (winPos.X <= 0)
            {
                winDX = 2;
            }
            else if (winPos.X >= GraphicsDevice.Viewport.Width - 150)
            {
                winDX = -2;
            }
            if (winPos.Y <= 0)
            {
                winDY = 2;
            }
            else if (winPos.Y >= GraphicsDevice.Viewport.Height - 20)
            {
                winDY = -2;
            }

            spriteBatch.DrawString(directionFont, string.Format("{0} WINS!", Players.First().Name), winPos, Players.First().Color);
        }

        private void DrawSidebar()
        {
            spriteBatch.Draw(sidebarTexture, new Rectangle(0, 0, 200, 500), Color.White);

            floatingHelpMessages.Draw(spriteBatch);

            DrawPlayerInfo();
            DrawHoverUnitDescription();
            DrawCurrentUnitDescription();
            DrawDirections();
        }

        private void DrawDirections()
        {
            spriteBatch.Draw(buttonATexture, new Rectangle(12, 380, 48, 48), Color.White);

            spriteBatch.DrawString(directionFont, "Select/", new Vector2(61, 382), Color.Black);
            spriteBatch.DrawString(directionFont, "Move", new Vector2(61, 402), Color.DeepSkyBlue);
            spriteBatch.DrawString(directionFont, "/", new Vector2(106, 402), Color.Black);
            spriteBatch.DrawString(directionFont, "Attack", new Vector2(118, 402), Color.Coral);

            spriteBatch.Draw(buttonYTexture, new Rectangle(25, 430, 48, 48), Color.White);

            spriteBatch.DrawString(directionFont, "End turn", new Vector2(74, 440), Color.Black);

        }

        private void DrawGrid(List<Point> movementTilesToTint, List<Point> attackTilesToTint)
        {
            THE_GRID.Draw(spriteBatch, movementTilesToTint, attackTilesToTint);
        }

        private void DrawPlayerInfo()
        {
            spriteBatch.DrawString(nameFont, string.Format("{0}'s turn!", CurrentPlayer.Name), new Vector2(50, 20), CurrentPlayer.Color);
        }

        private void DrawHoverUnitDescription()
        {
            Unit unit = AllUnits.Where(u => cursorGridLocation == u.GridLocation).FirstOrDefault();

            if (unit != null)
            {
                spriteBatch.Draw(unit.UnitTexture, new Rectangle(50, 150, GRID_TILE_SIZE, GRID_TILE_SIZE), Color.White);

                spriteBatch.DrawString(nameFont, "Hover:", new Vector2(70, 120), unit.Owner.Color);
                spriteBatch.DrawString(titleFont, unit.Title, new Vector2(50, 190), unit.Owner.Color);
                spriteBatch.DrawString(nameFont, unit.HitPoints, new Vector2(100, 180), unit.Owner.Color);
                spriteBatch.DrawString(titleFont, string.Format("Attack: {0}", unit.AttackPower), new Vector2(100, 200), unit.Owner.Color);
                spriteBatch.DrawString(titleFont, string.Format("Move:   {0}", unit.MaxMove), new Vector2(100, 215), unit.Owner.Color);
            }
        }

        private void DrawCurrentUnitDescription()
        {
            if (CurrentSelectedUnit != null)
            {
                spriteBatch.Draw(CurrentSelectedUnit.UnitTexture, new Rectangle(50, 290, GRID_TILE_SIZE, GRID_TILE_SIZE), Color.White);

                spriteBatch.DrawString(nameFont, "Selected:", new Vector2(70, 260), CurrentSelectedUnit.Owner.Color);
                spriteBatch.DrawString(titleFont, CurrentSelectedUnit.Title, new Vector2(50, 330), CurrentSelectedUnit.Owner.Color);
                spriteBatch.DrawString(nameFont, CurrentSelectedUnit.HitPoints, new Vector2(100, 310), CurrentSelectedUnit.Owner.Color);
                spriteBatch.DrawString(titleFont, string.Format("Attack: {0}", CurrentSelectedUnit.AttackPower), new Vector2(100, 340), CurrentSelectedUnit.HasAttacked ? Color.FromNonPremultiplied(0, 85, 0, 80) : Color.FromNonPremultiplied(0, 100, 0, 255));
                spriteBatch.DrawString(titleFont, string.Format("Move:   {0}", CurrentSelectedUnit.MaxMove), new Vector2(100, 355), CurrentSelectedUnit.HasMoved ? Color.FromNonPremultiplied(0, 85, 0, 80) : Color.FromNonPremultiplied(0, 100, 0, 255));

            }
        }

        private void DrawCursor()
        {
            cursorAnimation.Position = new Vector2(GRID_TILE_SIZE * cursorGridLocation.X, GRID_TILE_SIZE * cursorGridLocation.Y);
            cursorAnimation.Draw(spriteBatch, SpriteEffects.None);
        }

        private void DrawUnits()
        {
            foreach (Unit unit in AllUnits)
            {
                unit.Draw(spriteBatch);
            }
        }
    }
}
